package task

import (
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"

	"gitee.com/sansaniot/ssiot-core/facade/runtime"
	"gitee.com/sansaniot/ssiot-core/logger"
	"github.com/shirou/gopsutil/v3/disk"

	"ssadmin/common/constant"
	"ssadmin/common/utils"
	"ssadmin/internal/admin/models"
)

// CleanFile 文件清理 (磁盘余量百分比，触发业务文件清理)
func CleanFile() {
	absPath, err := filepath.Abs(constant.FilePath)
	if err != nil {
		logger.Error(err)
		return
	}
	exist := dirExists(absPath)
	if !exist {
		return
	}
	db := runtime.Runtime.GetDbByKey("db")
	if db == nil {
		return
	}
	// 存储根目录占用情况
	dirName := filepath.Dir(absPath)
	usage, err := disk.Usage(dirName)
	if err != nil {
		logger.Error(err)
		return
	}
	sycConfModel := &models.SysConf{}
	sycConfModel.Orm = db
	sysConf, err := sycConfModel.GetByKeyList([]string{constant.FileCleanThreshold, constant.FileCleanType})
	if err != nil {
		logger.Error(err)
		return
	}
	// 默认余量百分之10触发
	threshold := float64(10)
	// 默认滚动清理
	cleanType := "roll"
	for _, conf := range sysConf {
		if conf.Key == constant.FileCleanThreshold {
			var td float64
			if td, err = strconv.ParseFloat(conf.Value, 64); err != nil {
				logger.Error(err)
			} else {
				threshold = td
			}
		}
		if conf.Key == constant.FileCleanType && conf.Value != "roll" {
			cleanType = "discard"
		}
	}
	logger.Infof("%s剩余空间%s%%", dirName, strconv.FormatFloat(100-usage.UsedPercent, 'f', 2, 64))
	logger.Infof("余量阈值%s%%", strconv.FormatFloat(threshold, 'f', 2, 64))
	notEnough := 100-usage.UsedPercent < threshold
	if !notEnough {
		constant.FileUploadDiscard = false
		return
	}
	// 需要清理的大小
	cleanSize := int64(float64(usage.Total) * (threshold - 100 + usage.UsedPercent))
	// 清理
	remove(cleanType, cleanSize)
}

func remove(cleanType string, cleanSize int64) {
	switch cleanType {
	case "discard":
		constant.FileUploadDiscard = true
		logger.Info("[开启上传文件丢弃.]")
	case "roll":
		absPath, _ := filepath.Abs(constant.FilePath)
		// 目录文件
		files, pathMap := walkDir(absPath)
		logger.Info("[开始滚动文件清理.]")
		if len(files) == 0 {
			logger.Infof("%s不存在任何文件需要清理", absPath)
			return
		}
		// 按修改时间排序(优先清理旧文件)
		sort.Slice(files, func(i, j int) bool {
			return files[i].ModTime().Before(files[j].ModTime())
		})
		// 删除文件 腾出空间
		for _, f := range files {
			//p := filepath.Join(absPath, f.Name())
			p, ok := pathMap[f.Name()]
			if !ok || strings.Contains(p, "/scada") || isIgnorePath(p) {
				continue
			}
			logger.Infof("清理%s", p)
			err := os.Remove(p)
			if err != nil {
				logger.Errorf("清理文件失败:%v", err.Error())
				continue
			}
			cleanSize -= f.Size()
			if cleanSize <= 0 {
				break
			}
		}
	}
}

func isIgnorePath(p string) bool {
	for _, igp := range constant.CleanIgnore {
		igp = utils.GetCommUtil().PathClear(igp)
		if strings.Contains(p, "/"+igp) {
			return true
		}
	}
	return false
}

func dirExists(path string) bool {
	info, err := os.Stat(path)
	if err != nil {
		if os.IsNotExist(err) {
			return false
		}
	}
	return info.IsDir()
}

func walkDir(dir string) ([]os.FileInfo, map[string]string) {
	files := make([]os.FileInfo, 0)
	pathMap := make(map[string]string)
	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if !info.IsDir() {
			files = append(files, info)
			pathMap[info.Name()] = path
		}
		return nil
	})
	if err != nil {
		logger.Errorf("文件目录查找异常:%v", err.Error())
	}
	return files, pathMap
}
